package wireguardcommon

import (
	"bytes"
	"crypto"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"fmt"
	"github.com/BurntSushi/toml"
	"github.com/aead/ecdh"
	"github.com/vishvananda/netlink"
	"golang.org/x/crypto/chacha20poly1305"
	"golang.org/x/crypto/curve25519"
	"golang.zx2c4.com/wireguard/wgctrl"
	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
	"io"
	"log"
	"net"
	"net/http"
	"net/netip"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

const (
	InitialPort                 = 32323
	WireguardListenPort         = 32322
	PersistentKeepAliveInterval = 32
	InterfaceName               = "wg3"
	Ip2                         = "5.161.133.68"
	Ip1                         = "192.168.1.152"
	Ip3                         = "127.0.0.1"
	IPRange                     = "192.168.88.1/24"
	ClientConfigFile            = "/etc/3nets.conf"
	pkFilename                  = "resources/managerPrivateKey"
	ConfFilename                = "resources/wg3.conf"
	ConfFilenameAlt             = "resources/wg3.alt.conf"
	deviceFormatStr             = `interface: %s (%s)
  public key: %s
  private key: (hidden)
  listening port: %d

`
	peerFormatStr = `peer: %s
  endpoint: %s
  allowed ips: %s
  latest handshake: %s
  transfer: %d B received, %d B sent

`
	deviceFormatStrWithSecrets = `[Interface] # %s (%s)
ListenPort = %d
PrivateKey = %s
Address = %s

`
	peerFormatStrWithSecrets = `[Peer]
PublicKey = %s
PresharedKey = %s
AllowedIps = %s
PersistentKeepalive = %d

`
)

const (
	ExitSetupSuccess = 0
	ExitSetupFailed  = 1
)

func GenerateKeyPair() (privateKey crypto.PrivateKey, publicKey crypto.PublicKey, err error) {
	if _, err = os.Stat(pkFilename); errors.Is(err, os.ErrNotExist) {
		fmt.Printf("managerPrivateKey not found\n")
		c25519 := ecdh.X25519()
		privateKey, publicKey, err = c25519.GenerateKey(rand.Reader)
		if err != nil {
			fmt.Printf("Failed to generate manager privateKey/public key pair: %s\n", err)
			return
		}

		var managerPrivateBytes [32]byte
		if ok := CheckType(&managerPrivateBytes, privateKey); !ok {
			panic("ecdh: unexpected type of manager public key")
		}

		var managerPublicBytes [32]byte
		if ok := CheckType(&managerPublicBytes, publicKey); !ok {
			panic("ecdh: unexpected type of manager public key")
		}

		managerDHPrivate := base64.StdEncoding.EncodeToString(managerPrivateBytes[:])
		managerDHPublic := base64.StdEncoding.EncodeToString(managerPublicBytes[:])
		fmt.Printf("managerDHPrivate: %s\n", managerDHPrivate)
		fmt.Printf("managerDHPublic: %s\n", managerDHPublic)
		dir := filepath.Dir(pkFilename)
		err = os.MkdirAll(dir, 0555)
		if err != nil {
			log.Fatalf("MkdirAll for privateKey key failed, %s", err)
		}
		err = os.WriteFile(pkFilename, []byte(managerDHPrivate), 0600)
		if err != nil {
			log.Fatalf("Write file for privateKey key failed, %s", err)
		}
	} else {
		fmt.Printf("managerPrivateKey found\n")
		var managerBytes []byte
		var managerPrivateBytes []byte
		var managerPublicBytes [32]byte
		managerBytes, err = os.ReadFile(pkFilename)
		if err != nil {
			log.Fatalf("Read file for privateKey key failed, %s", err)
		}
		fmt.Printf("managerBytes = %s\n", managerBytes)
		managerPrivateBytes, err = base64.StdEncoding.DecodeString(string(managerBytes))
		if err != nil {
			log.Fatalf("Could not decode managerBytes, err:%s", err)
		}
		privateKey = managerPrivateBytes

		// build public key from privateKey key
		publicKey, err = curve25519.X25519(managerPrivateBytes, curve25519.Basepoint)
		if err != nil {
			log.Fatalf("Could not build public from managerPrivate, err:%s", err)
		}

		if ok := CheckType(&managerPublicBytes, publicKey); !ok {
			panic("ecdh: unexpected type of manager public key")
		}

		managerDHPrivate := base64.StdEncoding.EncodeToString(managerPrivateBytes[:])
		managerDHPublic := base64.StdEncoding.EncodeToString(managerPublicBytes[:])
		fmt.Printf("managerDHPublic: %s\n", managerDHPublic)
		fmt.Printf("managerDHPrivate: %s\n", managerDHPrivate)
	}
	return
}

func CheckType(key *[32]byte, typeToCheck interface{}) (ok bool) {
	switch t := typeToCheck.(type) {
	case [32]byte:
		copy(key[:], t[:])
		ok = true
	case *[32]byte:
		copy(key[:], t[:])
		ok = true
	case []byte:
		if len(t) == 32 {
			copy(key[:], t)
			ok = true
		}
	case *[]byte:
		if len(*t) == 32 {
			copy(key[:], *t)
			ok = true
		}
	}
	return
}

func GenerateSignature(url string, message string) (signature string, err error) {
	if url == "" {
		url = "http://test1.jagat.me:8123/nacl-sign"
	}
	requestJson := "{\"message\": \"" + message + "\"}"
	response, err := http.Post(url, "application/json", bytes.NewBuffer([]byte(requestJson)))
	if err != nil {
		log.Fatalf("Cant reach the url for generating signature, %s", err)
	}
	signatureBytes, err := io.ReadAll(response.Body)
	signature = string(signatureBytes)
	return
}
func VerifySignature(url string, signature string) (message string, status bool, err error) {
	if url == "" {
		url = "http://test1.jagat.me:8123/nacl-verify"
	}
	status = false
	requestJson := "{\"signature\":\"" + signature + "\"}"
	response, err := http.Post(url, "application/json", bytes.NewBuffer([]byte(requestJson)))
	if err != nil {
		log.Fatalf("Cant reach the url for verifying signature, %s", err)
	}

	messageBytes, err := io.ReadAll(response.Body)
	message = string(messageBytes)
	if response.StatusCode == 200 {
		status = true
		return
	}
	return
}

func VerifySignatureRegex(url string, regexMatchString string, encrypted string) (status bool, err error) {
	status = false
	var message string
	message, err = DecryptMessage(url, encrypted)
	fmt.Printf("descrypted message : --%s--\n", message)
	if err == nil {
		var re = regexp.MustCompile(regexMatchString)
		if re.MatchString(message) {
			err = nil
			status = true
		}
	}
	return
}

func EncryptMessage(url string, message string) (encrypted string, err error) {
	if url == "" {
		url = "http://test1.jagat.me:8123/nacl-encrypt"
	}
	requestJson := "{\"message\": \"" + message + "\"}"
	response, err := http.Post(url, "application/json", bytes.NewBuffer([]byte(requestJson)))
	if err != nil {
		log.Fatalf("Cant reach the url for encrypting, %s", err)
	}
	encryptedBytes, err := io.ReadAll(response.Body)
	encrypted = string(encryptedBytes)
	return
}

func DecryptMessage(url string, encrypted string) (message string, err error) {
	if url == "" {
		url = "http://test1.jagat.me:8123/nacl-decrypt"
	}
	requestJson := "{\"encrypted\":\"" + encrypted + "\"}"
	response, err := http.Post(url, "application/json", bytes.NewBuffer([]byte(requestJson)))
	if err != nil {
		log.Fatalf("Cant reach the url for decrypting, %s", err)
	}
	decryptedBytes, err := io.ReadAll(response.Body)
	message = string(decryptedBytes)
	return
}

func GenPskOnEdge(managerDHPublicStr string) (presharedKey string, edgeDHPublic string, err error) {
	c25519 := ecdh.X25519()
	var edgePrivate crypto.PrivateKey
	var edgePublic crypto.PublicKey

	edgePrivate, edgePublic, err = c25519.GenerateKey(rand.Reader)

	var edgePublicBytes [32]byte
	if ok := CheckType(&edgePublicBytes, edgePublic); !ok {
		panic("ecdh: unexpected type of edge public key")
	}
	edgeDHPublic = base64.StdEncoding.EncodeToString(edgePublicBytes[:])
	//fmt.Printf("edgeDHPublic: %s\n", edgeDHPublic)

	if err != nil {
		fmt.Printf("Failed to generate edge private/public key pair: %s\n", err)
		return
	}

	managerDHPublic, err := base64.StdEncoding.DecodeString(managerDHPublicStr)
	if err != nil {
		fmt.Printf("manager public key is could not be decoded: %s\n", err)
	}

	if err = c25519.Check(managerDHPublic); err != nil {
		fmt.Printf("manager public key is not on the curve: %s\n", err)
	}
	secret := c25519.ComputeSecret(edgePrivate, managerDHPublic)
	presharedKey = base64.StdEncoding.EncodeToString(secret)
	return
}

func GenPskOnManager(edgeDHPublicStr string,
	managerPrivate crypto.PrivateKey) (presharedKey string, err error) {
	c25519 := ecdh.X25519()

	edgeDHPublic, err := base64.StdEncoding.DecodeString(edgeDHPublicStr)
	if err != nil {
		fmt.Printf("edge public key is could not be decoded: %s\n", err)
	}

	if err = c25519.Check(edgeDHPublic); err != nil {
		fmt.Printf("edge public key is not on the curve: %s\n", err)
	}
	secret := c25519.ComputeSecret(managerPrivate, edgeDHPublic)
	presharedKey = base64.StdEncoding.EncodeToString(secret)
	return
}

func WriteConn(conn net.Conn, message []byte) (writeLen int, err error) {
	// Send manager DH public
	writeLen, err = conn.Write(message)
	if err != nil {
		fmt.Printf("Errored when send data on %s, error=%d\n", conn, err)
		return
	}
	return
}
func ReadConn(conn net.Conn, maxLength int) (responseStr string, responseLen int, err error) {
	// Make a buffer to hold incoming data.
	edgeResponse := make([]byte, maxLength+32)
	// Read the incoming connection into the buffer.
	responseLen, err = conn.Read(edgeResponse)
	if err != nil {
		fmt.Println("Error reading:", err.Error())
		CloseConn(conn)
		return
	}
	if responseLen > maxLength {
		fmt.Printf("read more than needed. responseLen = %d\n", responseLen)
		CloseConn(conn)
		return
	}
	responseStr = string(edgeResponse[:responseLen])
	return
}

func CloseConn(conn net.Conn) {
	err := conn.Close()
	if err != nil {
		fmt.Printf("error closing conn. ignored\n")
		return
	}
}

func GetCurrentConfWithSecrets() (conf string, err error) {
	c, err := wgctrl.New()
	if err != nil {
		log.Fatalf("failed to open wgctrl: %v", err)
		return
	}
	defer c.Close()

	var retStr strings.Builder
	var devices []*wgtypes.Device
	devices, err = c.Devices()
	if err != nil {
		log.Fatalf("failed to get devices: %v", err)
		return
	}

	for _, d := range devices {
		var link netlink.Link
		var addrs []net.IP
		var netaddrs []netlink.Addr
		link, err = netlink.LinkByName(d.Name)
		if err != nil {
			fmt.Printf("netlink.LinkByName(%s) fialed, err: %s\n", d.Name, err)
		} else {
			netaddrs, err = netlink.AddrList(link, netlink.FAMILY_V4)
			if err != nil {
				fmt.Printf("netlink.AddrList(%s) fialed, err: %s\n", d.Name, err)
			}
			for _, a := range netaddrs {
				addrs = append(addrs, a.IPNet.IP)
			}
		}
		retStr.WriteString(fmt.Sprintf(
			deviceFormatStrWithSecrets,
			d.Name,
			d.Type.String(),
			d.ListenPort,
			d.PrivateKey.String(),
			addrs[0],
		))

		for _, p := range d.Peers {
			retStr.WriteString(fmt.Sprintf(
				peerFormatStrWithSecrets,
				p.PublicKey.String(),
				p.PresharedKey.String(),
				// TODO: get right endpoint with getnameinfo.
				ipsString(p.AllowedIPs),
				int(p.PersistentKeepaliveInterval.Seconds()),
			))
		}
	}
	conf = retStr.String()
	return
}

func GetCurrentConf() (conf string, err error) {
	c, err := wgctrl.New()
	if err != nil {
		log.Fatalf("failed to open wgctrl: %v", err)
		return
	}
	defer c.Close()

	var retStr strings.Builder
	var devices []*wgtypes.Device
	devices, err = c.Devices()
	if err != nil {
		log.Fatalf("failed to get devices: %v", err)
		return
	}

	for _, d := range devices {
		retStr.WriteString(fmt.Sprintf(
			deviceFormatStr,
			d.Name,
			d.Type.String(),
			d.PublicKey.String(),
			d.ListenPort))

		for _, p := range d.Peers {
			retStr.WriteString(fmt.Sprintf(
				peerFormatStr,
				p.PublicKey.String(),
				// TODO(mdlayher): get right endpoint with getnameinfo.
				p.Endpoint.String(),
				ipsString(p.AllowedIPs),
				p.LastHandshakeTime.String(),
				p.ReceiveBytes,
				p.TransmitBytes))
		}
	}
	conf = retStr.String()
	return
}

func ipsString(ipns []net.IPNet) string {
	ss := make([]string, 0, len(ipns))
	for _, ipn := range ipns {
		ss = append(ss, ipn.String())
	}

	return strings.Join(ss, ", ")
}

func ResolveOutgoingInterface() string {
	routes, _ := netlink.RouteGet(net.ParseIP("1.1.1.1"))
	for _, route := range routes {
		log.Printf("connected on linkindexlink %d\n", route.LinkIndex)
		link, err := netlink.LinkByIndex(route.LinkIndex)
		if err != nil {
			fmt.Printf("unable to get the link. error = %s\n", err)
		} else {
			log.Printf("connected on olink %s\n", link.Attrs().Name)
			return link.Attrs().Name
		}
	}
	return ""
}

func ResolveHostIp() string {
	netInterfaceAddresses, err := net.InterfaceAddrs()

	if err != nil {
		return ""
	}

	for _, netInterfaceAddress := range netInterfaceAddresses {
		fmt.Printf("netInterfaceAddress = %s\n", netInterfaceAddress)
		networkIp, ok := netInterfaceAddress.(*net.IPNet)
		if ok && !networkIp.IP.IsLoopback() && networkIp.IP.To4() != nil {
			ip := networkIp.IP.String()
			fmt.Println("Resolved Host IP: " + ip)
			return ip
		}
	}
	return ""
}

func CreateKeyPairFromSeed(seed string) (privateKeyStr string, publicKeyStr string, err error) {
	var edgePublicKey []byte
	seedHash := sha256.Sum256([]byte(seed))
	//create a curve25519 point (for privateKey)
	seedHash[0] &= 248
	seedHash[31] &= 127
	seedHash[31] |= 64
	privateKeyStr = base64.StdEncoding.EncodeToString(seedHash[:])
	fmt.Printf("privateKey = %s\n", privateKeyStr)

	// build publickey from privatekey
	edgePublicKey, err = curve25519.X25519(seedHash[:], curve25519.Basepoint)
	if err != nil {
		log.Fatalf("curve25519.X25519() failed: %v", err)
		return
	}
	publicKeyStr = base64.StdEncoding.EncodeToString(edgePublicKey)
	fmt.Printf("publicKey  = %s\n", publicKeyStr)

	return
}

func Chacha20poly1305Decrypt(key []byte, encryptedMsg []byte) (message []byte, err error) {
	var aead cipher.AEAD
	aead, err = chacha20poly1305.NewX(key)
	if err != nil {
		fmt.Printf("failed to create aead from key")
		return
	}
	if len(encryptedMsg) < aead.NonceSize() {
		panic("ciphertext too short")
	}

	// Split nonce and ciphertext.
	nonce, ciphertext := encryptedMsg[:aead.NonceSize()], encryptedMsg[aead.NonceSize():]

	// Decrypt the message and check it wasn't tampered with.
	message, err = aead.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		return
	}
	return
}

func Chacha20poly1305Encrypt(key []byte, message []byte) (encryptedMsg []byte, err error) {
	var aead cipher.AEAD
	aead, err = chacha20poly1305.NewX(key)
	if err != nil {
		fmt.Printf("failed to create aead from key")
		return
	}
	// Select a random nonce, and leave capacity for the ciphertext.
	nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(message)+aead.Overhead())
	if _, err = rand.Read(nonce); err != nil {
		return
	}

	// Encrypt the message and append the ciphertext to the nonce.
	encryptedMsg = aead.Seal(nonce, nonce, message, nil)
	return
}

type WireguardLink struct {
	netlink.LinkAttrs
}

func (generic *WireguardLink) Attrs() *netlink.LinkAttrs {
	return &generic.LinkAttrs
}

func (generic *WireguardLink) Type() string {
	return "wireguard"
}

func MakeWireguardInterface() (managerPrivate crypto.PrivateKey, managerPublic crypto.PrivateKey, outgoingLink netlink.Link, wireguardLink netlink.Link, err error) {
	la := netlink.NewLinkAttrs()
	la.Name = InterfaceName
	linkExists := true
	link, err := netlink.LinkByName(la.Name)
	if err != nil || link == nil {
		linkExists = false
	}

	var outgoingInterfaceName string
	managerPrivate, managerPublic, err = GenerateKeyPair()
	if !linkExists {
		fmt.Printf("interface %s does not exist. Creating\n", la.Name)
		link := WireguardLink{la}
		err = netlink.LinkAdd(&link)
		if err != nil {
			fmt.Printf("Failed to add link for %s, err: %s\n", la.Name, err)
			return nil, nil, nil, nil, err
		}
		outgoingInterfaceName = ResolveOutgoingInterface()
		wireguardLink, err = netlink.LinkByName(la.Name)

		c, _ := wgctrl.New()
		defer c.Close()
		if _, err = os.Stat(ConfFilename); err == nil {
			_, err = ConfigureWgFromSavedConfig()
			if err != nil {
				fmt.Println("Error loading with saved config, err:", err.Error())
			}

			var device *wgtypes.Device
			device, err = c.Device(InterfaceName)
			if err != nil {
				fmt.Printf("Error finding device at %s, err:%s", InterfaceName, err)
			}

			privateBytes, _ := base64.StdEncoding.DecodeString(device.PrivateKey.String())
			publicBytes, _ := base64.StdEncoding.DecodeString(device.PrivateKey.PublicKey().String())
			managerPrivate = privateBytes
			managerPublic = publicBytes
		} else {
			//////////////////////////////////////////////////////////////////////
			//configure wireguard interface with specific details
			//////////////////////////////////////////////////////////////////////

			var privateBytes [32]byte
			if ok := CheckType(&privateBytes, managerPrivate); !ok {
				panic("ecdh: unexpected type of manager public key")
			}

			var key wgtypes.Key
			key, err = wgtypes.NewKey(privateBytes[:])
			port := WireguardListenPort

			cfg := wgtypes.Config{
				PrivateKey:   &key,
				ListenPort:   &port,
				ReplacePeers: true,
			}

			if err != nil {
				return
			}
			err = c.ConfigureDevice(la.Name, cfg)
			if err != nil {
				fmt.Println("wgctrlclient ConfigureDevice failed:", err.Error())
				return
			}
			//////////////////////////////////////////////////////////////////////
			//end of configure wireguard interface with manager specific details
			//////////////////////////////////////////////////////////////////////
		}
		//Add the ip addr
		managerIp, _ := GetFirstAddr()
		fmt.Printf("managerIP : %s\n", managerIp)
		err = AddLinkAddress(wireguardLink, managerIp.String(), "")
		if err != nil {
			fmt.Printf("11 Failed to set address to %s, err: %s\n", la.Name, err)
			return nil, nil, nil, nil, err
		}
	} else {
		fmt.Printf("interface %s exists\n", la.Name)
		outgoingInterfaceName = ResolveOutgoingInterface()
		wireguardLink, _ = netlink.LinkByName(la.Name)
	}

	// bring the interface up
	err = netlink.LinkSetUp(wireguardLink)
	if err != nil {
		fmt.Printf("Could not set the link up: %s\n", err)
		return nil, nil, nil, nil, err
	}

	// add a default route for the wireguard interface
	foundRoute := false
	_, dst, _ := net.ParseCIDR(IPRange)
	route := netlink.Route{LinkIndex: wireguardLink.Attrs().Index, Dst: dst, Scope: netlink.SCOPE_LINK}
	//fmt.Printf("to be added route= %s\n", route.String())

	routes, err := netlink.RouteList(link, netlink.FAMILY_V4)
	for _, r := range routes {
		//fmt.Printf("existing route= %s\n", r.String())
		if CompareRoutes(route, r) {
			foundRoute = true
			break
		}
	}

	if !foundRoute {
		if err := netlink.RouteAdd(&route); err != nil {
			fmt.Printf("Failed to add a default route. err=%s\n", err)
			return nil, nil, nil, nil, err
		}
	}
	outgoingLink, _ = netlink.LinkByName(outgoingInterfaceName)
	return
}

// ipNetEqual returns true iff both IPNet are equal
func ipNetEqual(ipn1 *net.IPNet, ipn2 *net.IPNet) bool {
	if ipn1 == ipn2 {
		return true
	}
	if ipn1 == nil || ipn2 == nil {
		return false
	}
	m1, _ := ipn1.Mask.Size()
	m2, _ := ipn2.Mask.Size()
	return m1 == m2 && ipn1.IP.Equal(ipn2.IP)
}

func printRoute(r netlink.Route) {
	fmt.Printf("LinkIndex  %d\n", r.LinkIndex)
	fmt.Printf("ILinkIndex %d\n", r.ILinkIndex)
	fmt.Printf("Scope      %s\n", r.Scope)
	fmt.Printf("Dst        %s\n", r.Dst)
	fmt.Printf("Src        %s\n", r.Src)
	fmt.Printf("Gw         %s\n", r.Gw)
	fmt.Printf("MultiPath  %s\n", r.MultiPath)
	fmt.Printf("Protocol   %d\n", r.Protocol)
	fmt.Printf("Priority   %d\n", r.Priority)
	fmt.Printf("Table      %d\n", r.Table)
	fmt.Printf("Type       %d\n", r.Type)
	fmt.Printf("Tos        %d\n", r.Tos)
	fmt.Printf("Flags      %d\n", r.Flags)
	fmt.Printf("MPLSDst    %d\n", r.MPLSDst)
	fmt.Printf("NewDst     %s\n", r.NewDst)
	fmt.Printf("Encap      %s\n", r.Encap)
	fmt.Printf("MTU        %d\n", r.MTU)
	fmt.Printf("AdvMSS     %d\n", r.AdvMSS)
	fmt.Printf("Hoplimit   %d\n", r.Hoplimit)
}

// Copied from netlink.Route.Equal and removed checks
// nexthopInfoSlice(r.MultiPath).Equal(x.MultiPath) &&
// r.Table == x.Table
func CompareRoutes(r netlink.Route, x netlink.Route) bool {
	return r.LinkIndex == x.LinkIndex &&
		r.ILinkIndex == x.ILinkIndex &&
		r.Scope == x.Scope &&
		ipNetEqual(r.Dst, x.Dst) &&
		r.Src.Equal(x.Src) &&
		r.Gw.Equal(x.Gw) &&
		r.Priority == x.Priority &&
		r.Tos == x.Tos &&
		r.Hoplimit == x.Hoplimit &&
		r.Flags == x.Flags &&
		(r.MPLSDst == x.MPLSDst || (r.MPLSDst != nil && x.MPLSDst != nil && *r.MPLSDst == *x.MPLSDst)) &&
		(r.NewDst == x.NewDst || (r.NewDst != nil && r.NewDst.Equal(x.NewDst))) &&
		(r.Encap == x.Encap || (r.Encap != nil && r.Encap.Equal(x.Encap)))
}

// CreateWireguardInterface TODO: use this in the function MakeWireguardInterface
func CreateWireguardInterface() (outgoingLink netlink.Link, wireguardLink netlink.Link, err error) {
	la := netlink.NewLinkAttrs()
	la.Name = InterfaceName
	linkExists := true
	link, err := netlink.LinkByName(la.Name)
	if err != nil || link == nil {
		linkExists = false
	}

	var outgoingInterfaceName string
	if !linkExists {
		fmt.Printf("interface %s does not exist. Creating\n", la.Name)
		link := WireguardLink{la}
		err = netlink.LinkAdd(&link)
		if err != nil {
			fmt.Printf("Failed to add link for %s, err: %s\n", la.Name, err)
			return
		}
	} else {
		fmt.Printf("interface %s exists\n", la.Name)
	}

	outgoingInterfaceName = ResolveOutgoingInterface()
	wireguardLink, err = netlink.LinkByName(la.Name)
	if err != nil {
		fmt.Printf("Could not find the link %S, err: %s\n", la.Name, err)
	}
	outgoingLink, _ = netlink.LinkByName(outgoingInterfaceName)

	routes, _ := netlink.RouteList(link, netlink.FAMILY_V4)
	for _, r := range routes {
		fmt.Printf("reote= %s\n", r.String())
	}

	// bring the interface up
	err = netlink.LinkSetUp(wireguardLink)
	if err != nil {
		fmt.Printf("Could not set the link up: %s\n", err)
	}

	// add a default route for the wireguard interface
	_, dst, _ := net.ParseCIDR(IPRange)
	route := netlink.Route{LinkIndex: wireguardLink.Attrs().Index, Dst: dst, Scope: netlink.SCOPE_LINK}
	if err1 := netlink.RouteAdd(&route); err1 != nil {
		fmt.Printf("Failed to add a default route. err=%s\n", err1)
		return
	}
	return

}
func ProvisionWireguardInterface(privateKey crypto.PrivateKey, ipaddrAndMask string) (outgoingLink netlink.Link, wireguardLink netlink.Link, err error) {
	outgoingLink, wireguardLink, err = CreateWireguardInterface()
	if err != nil {
		fmt.Printf("Failed to add link for %s, err: %s\n", InterfaceName, err)
		return
	}

	err = AddLinkAddress(wireguardLink, ipaddrAndMask, "")
	if err != nil {
		fmt.Printf("12 Failed to set address to %s, err: %s\n", InterfaceName, err)
		return nil, nil, err
	}

	var privateBytes [32]byte
	if ok := CheckType(&privateBytes, privateKey); !ok {
		panic("ecdh: unexpected type of manager public key")
	}

	key, _ := wgtypes.NewKey(privateBytes[:])
	port := WireguardListenPort
	cfg := wgtypes.Config{PrivateKey: &key, ListenPort: &port, ReplacePeers: true}

	var wgctrlClient *wgctrl.Client
	wgctrlClient, err = wgctrl.New()
	if err != nil {
		return
	}
	err = wgctrlClient.ConfigureDevice(InterfaceName, cfg)
	if err != nil {
		fmt.Printf("wgctrlclient ConfigureDevice failed, err=%s\n", err)
		return
	}

	// bring the interface up
	err = netlink.LinkSetUp(wireguardLink)
	if err != nil {
		fmt.Printf("Could not set the link up: %s\n", err)
		return nil, nil, err
	}
	return
}

func AddPeer(presharedKey string, edgePublicStr string, endpointIp string, allowedIp string, replace bool) {
	ipNet, err := ConstructIPAndMask(allowedIp)
	if err != nil {
		fmt.Println("ParseCIDR() failed:", err.Error())
		return
	}
	var allowedIps []net.IPNet
	allowedIps = append(allowedIps, *ipNet)

	presharedKeyBytes, _ := base64.StdEncoding.DecodeString(presharedKey)
	edgePublicBytes, _ := base64.StdEncoding.DecodeString(edgePublicStr)

	key, _ := wgtypes.NewKey(edgePublicBytes)
	pk, _ := wgtypes.NewKey(presharedKeyBytes)
	var dur = time.Second * PersistentKeepAliveInterval
	peerConfig := wgtypes.PeerConfig{
		PublicKey:                   key,
		PresharedKey:                &pk,
		ReplaceAllowedIPs:           true,
		PersistentKeepaliveInterval: &dur,
		AllowedIPs:                  allowedIps,
	}

	var ipEndpoint net.IP
	if endpointIp != "" {
		ipEndpoint = net.ParseIP(endpointIp)
		peerConfig.Endpoint = &net.UDPAddr{
			IP:   ipEndpoint,
			Port: WireguardListenPort,
		}
	}

	var peers []wgtypes.PeerConfig
	peers = append(peers, peerConfig)
	cfg := wgtypes.Config{
		ReplacePeers: replace,
		Peers:        peers,
	}

	wgctrlClient, err := wgctrl.New()
	if err != nil {
		fmt.Println("wgctrl.New() failed:", err.Error())
		return
	}
	err = wgctrlClient.ConfigureDevice(InterfaceName, cfg)
	if err != nil {
		fmt.Println("wgctrlclient ConfigureDevice failed:", err.Error())
		return
	}
}

func HasLinkAddress(link netlink.Link, address string) (exists bool, err error) {
	exists = false
	fmt.Printf("AddLinkAddress(linkAddressAndMask=%s)\n", address)
	var netmask *net.IPNet
	netmask, _ = ConstructIPAndMask(address)
	var addr = &netlink.Addr{IPNet: netmask}
	var addrs []netlink.Addr
	addrs, err = netlink.AddrList(link, netlink.FAMILY_V4)
	if err != nil {
		fmt.Printf("14 Failed to get list of addresses from %s, err: %s\n", InterfaceName, err)
		return
	}
	for _, a := range addrs {
		if addr.Equal(a) {
			exists = true
			return
		}
	}
	return
}

func AddLinkAddress(link netlink.Link, linkAddressAndMask string, peerAddress string) (err error) {
	//////////////////////////////////////////////////////////////////////
	//configure wireguard interface with manager specific details
	//////////////////////////////////////////////////////////////////////
	fmt.Printf("AddLinkAddress(linkAddressAndMask=%s)\n", linkAddressAndMask)
	var netmask *net.IPNet
	netmask, _ = ConstructIPAndMask(linkAddressAndMask)
	var addr = &netlink.Addr{IPNet: netmask}

	if peerAddress != "" {
		fmt.Printf("peerAddress %s\n", peerAddress)
		peerAddr, _ := ConstructIPAndMask(peerAddress)
		fmt.Printf("peerAddr %s\n", peerAddr)
		addr.Peer = peerAddr
	}
	err = netlink.AddrAdd(link, addr)
	if err != nil {
		fmt.Printf("14 Failed to set address to %s, err: %s\n", InterfaceName, err)
		return
	}
	return
}

func ConstructIPAndMask(inputStr string) (netipnet *net.IPNet, err error) {
	fmt.Printf("inputStr=%s\n", inputStr)
	var netipAddr net.IP
	netipAddr, netipnet, err = net.ParseCIDR(inputStr)
	if err != nil {
		netipAddr = net.ParseIP(inputStr)
		if netipAddr == nil {
			fmt.Printf("net.ParseIP(inputStr=%s) failed, err: %s\n", inputStr, err)
			return
		} else {
			netipAddr, netipnet, _ = net.ParseCIDR(inputStr + "/32")
			err = nil
		}
	}
	netipnet.IP = netipAddr
	return
}

func FindPeerByPublicKey(pubkey string) (*wgtypes.Peer, error) {
	peerKeyMap, err := GetPeersByPublicKey()
	if err != nil {
		return nil, err
	}
	peer1, found := peerKeyMap[pubkey]
	if found {
		return &peer1, nil
	}
	return nil, errors.New("peer not found")
}

func GetPeersByPublicKey() (peerKeyMap map[string]wgtypes.Peer, err error) {
	wgctrlClient, err := wgctrl.New()
	if err != nil {
		fmt.Printf("Failed to find the default wireguard device %s, err: %s\n", InterfaceName, err)
		return nil, err
	}
	defaultDevice, err := wgctrlClient.Device(InterfaceName)
	if err != nil {
		fmt.Printf("Failed to find the default wireguard device %s, err: %s\n", InterfaceName, err)
		return nil, err
	}
	devices, err := wgctrlClient.Devices()
	if err != nil {
		return
	}

	for _, device := range devices {
		fmt.Printf("device = %s\n", device.Name)
		fmt.Printf("Peers(%d):\n", len(device.Peers))
		for _, p := range device.Peers {
			fmt.Printf(" public=%s, psk=%s\n", p.PublicKey, p.PresharedKey)
			fmt.Printf(" ips=%s, endpoint=%s\n", p.AllowedIPs, p.Endpoint)
		}
	}

	peerKeyMap = make(map[string]wgtypes.Peer)
	var peers = defaultDevice.Peers
	for _, p := range peers {
		peerPubkey := base64.StdEncoding.EncodeToString(p.PublicKey[:])
		peerKeyMap[peerPubkey] = p
	}
	return
}
func GetPeersByIp() (peerIpMap map[netip.Addr]wgtypes.Peer, err error) {
	wgctrlClient, err := wgctrl.New()
	if err != nil {
		fmt.Printf("Failed to find the default wireguard device %s, err: %s\n", InterfaceName, err)
		return nil, err
	}
	defaultDevice, err := wgctrlClient.Device(InterfaceName)
	if err != nil {
		fmt.Printf("Failed to find the default wireguard device %s, err: %s\n", InterfaceName, err)
		return nil, err
	}
	devices, err := wgctrlClient.Devices()
	if err != nil {
		return
	}

	for _, device := range devices {
		fmt.Printf("device = %s\n", device.Name)
		fmt.Printf("Peers(%d):\n", len(device.Peers))
		for _, p := range device.Peers {
			fmt.Printf(" public=%s, psk=%s\n", p.PublicKey, p.PresharedKey)
			fmt.Printf(" ips=%s, endpoint=%s\n", p.AllowedIPs, p.Endpoint)
		}
	}

	peerIpMap = make(map[netip.Addr]wgtypes.Peer)
	var peers = defaultDevice.Peers
	for _, p := range peers {
		allowedIp, err := netip.ParseAddr(p.AllowedIPs[0].IP.String())
		if err != nil {
			fmt.Printf("failed to parse %s\n", p.AllowedIPs[0].IP.String())
		}
		peerIpMap[allowedIp] = p
	}

	return
}

func GetFirstAddr() (netip.Addr, *net.IPNet) {
	_, iPRange, _ := net.ParseCIDR(IPRange)
	ipFirst, _ := netip.ParseAddr(iPRange.IP.String())
	return ipFirst.Next(), iPRange
}

func FindUnusedIp() (ipNext netip.Addr, err error) {
	peers, _ := GetPeersByIp()
	ipNext, ipRange := GetFirstAddr()

	/*
		for ip, p := range peers {
			fmt.Printf("ip = %s, peer=%s\n", ip, p)
		}
	*/

	ipNext = ipNext.Next()
	ip := net.ParseIP(ipNext.String())
	for {
		//fmt.Printf("ipNext = %s, ip=%s\n", ipNext, ip)
		if _, ok := peers[ipNext]; !ok {
			//fmt.Printf("ipNext=%s, ip=%s is not in peers\n", ipNext, ip)
			if ipRange.Contains(ip) {
				//fmt.Printf("ipRange container ip=%s\n", ip)
				return ipNext, nil
			} else {
				//fmt.Printf("ipRange does not contain ip=%s\n", ip)
				break
			}
		} else {
			//fmt.Printf("ipNext=%s, ip=%s is in peers\n", ipNext, ip)
		}
		ipNext = ipNext.Next()
		ip = net.ParseIP(ipNext.String())

	}
	return ipNext, errors.New("cant find unused ip address")
}

type ClientConfig struct {
	Signature   string
	InitialPort int
	ManagerIP   string
}

func ReadClientConfig(path string) (conf ClientConfig, meta toml.MetaData, err error) {
	meta, err = toml.DecodeFile(path, &conf)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
	}
	return
}

func VerifyVmInstanceId(instanceId string) (valid bool) {
	//TODO:check the instanceId with the list of instances we created
	//For now return true
	if len(instanceId) > 2 {
		return true
	} else {
		return false
	}
}

func StoreWgConfig() (err error) {
	//////////////////////////////////////////////
	//Write everything we have, to a file
	currentConf, err := GetCurrentConfWithSecrets()
	if err != nil {
		log.Printf("error getting config, %s", err)
		return
	}
	err = os.WriteFile(ConfFilename, []byte(currentConf), 0600)
	if err != nil {
		log.Printf("Write file for config key failed, %s", err)
	}
	return
}

func StoreWgConfigUsingWgCommand() (err error) {
	/// get conf using /usr/bin/wg
	cmd := exec.Command("/usr/bin/wg", "showconf", InterfaceName)
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err = cmd.Run()
	if err != nil {
		fmt.Printf("Failed to run wg showconf wg3: %s\n", err)
		fmt.Printf("stderr %s\n", stderr.String())
		os.Exit(ExitSetupFailed)
		return
	}
	err = os.WriteFile(ConfFilename, stdout.Bytes(), 0600)
	return
}

func ConfigureWgFromSavedConfig() (specialContent []string, err error) {
	/// set conf using /usr/bin/wg
	confBytes, err := os.ReadFile(ConfFilename)
	if err != nil {
		fmt.Printf("Error reading file %s, err: %s\n", ConfFilename, err)
		return
	}
	var re = regexp.MustCompile(`(?m)^\s*Address\s*=\s*(?:\d{1,3}\.){3}\d{1,3}\s*$`)
	confStr := string(confBytes)
	var removedLines []string
	removedLines = append(removedLines, re.FindAllString(confStr, -1)...)
	fmt.Printf("removedLines = %s\n", removedLines)
	for _, s := range removedLines {
		specialContent = append(specialContent, strings.ReplaceAll(s, " ", ""))
	}
	confStr1 := re.ReplaceAllString(confStr, "")
	fmt.Printf("confStr1 = %s\n", confStr1)
	tmpFileName := ConfFilename + ".tmp"
	fmt.Printf("tmpFileName = %s\n", tmpFileName)
	err1 := os.WriteFile(tmpFileName, []byte(confStr1), 0660)
	if err1 != nil {
		fmt.Printf("Error writing curated file %s, err: %s\n", tmpFileName, err)
		return
	}
	cmd := exec.Command("/usr/bin/wg", "setconf", InterfaceName, tmpFileName)
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	fmt.Printf("running %s\n", cmd)
	err = cmd.Run()
	_ = os.Remove(tmpFileName)
	if err != nil {
		fmt.Printf("Failed to run %s, err: %s\n", cmd, err)
		fmt.Printf("stderr %s\n", stderr.String())
		os.Exit(ExitSetupFailed)
	}
	return
}
